<?php

/**
 * SmartPHP framework
 *
 * @name SmartPHP framework
 * @package SmartPHP
 * @author 费尔 <admin@smartphp.cn>
 * @version 1.0 Beta
 * @link http://www.smartphp.cn
 * @copyright smartphp.cn
 * @license <a href="http://www.smartphp.cn">smartphp</a>
 */

namespace Base;

/**
 * View
 *
 * @author 费尔 <admin@smartphp.cn>
 */
class View {

    public static function init() {
        
    }

    // 模板变量
    private $_vars = [];

    /**
     * 赋值
     *
     * @param string|array $var
     * @param mixed $value
     */
    public function assign($var, $value = NULL) {
        is_array($var) ? ($this->_vars = array_merge($this->_vars, $var)) : ($this->_vars[$var] = $value);
    }

    /**
     * 获取模板变量
     *
     * @param string $name
     * @return NULL|mixed
     */
    public function getVar($name = '') {
        return $name === '' ? $this->_vars : ($this->_vars[$name] ?? NULL);
    }

    /**
     * 取得输出内容
     *
     * @param string $template 模板
     * @param string $id 识别ID
     * @return string
     */
    public function fetch($template = '', $id = '') {
        $templateFile = $this->getTemplateFile($template);
        if (!is_null($this->_cache) && $this->isCache($template)) {
            $cacheFile = $this->getCacheFile($this->getTemplateFile($template), $id);
            $content = File::read($cacheFile);
        } else {
            $compileFile = $this->getCompileFile($templateFile, $id);
            $_startTime = microtime(true);
            $this->compile($templateFile, $compileFile);
            $this->_compileTime = App::getUsageTime($_startTime, microtime(true));
            ob_start();
            ob_implicit_flush(0);
            extract($this->_vars, EXTR_OVERWRITE);
            extract(['_config' => Config::getAll()], EXTR_OVERWRITE);
            extract(['_lang' => Lang::getAll()], EXTR_OVERWRITE);
            try {
                include $compileFile;
            } catch (\Exception $e) {
                trigger_error($e->getMessage());
            }
            $content = ob_get_clean();
            if (!is_null($this->_cache))
                File::write($cacheFile, $content);
        }
        return $content;
    }

    private $_cache = NULL;

    /**
     * 启动缓存 默认配制参数(单位秒) 0永远缓存 缓存一个小时：3600
     *
     * @param int $time
     */
    public function inCache($time = NULL) {
        $this->_cache = is_null($time) ? Config::get('view.cachetime') : $time;
    }

    /**
     * 检测是否被缓存
     *
     * @param string $template 模板
     * @param string $id 识别ID
     * @return boolean
     */
    public function isCache($template = '', $id = '') {
        $cacheFile = $this->getCacheFile($this->getTemplateFile($template), $id);
        if (!file_exists($cacheFile))
            return false;
        $_cache = is_null($this->_cache) ? $this->_cache : Config::get('view.cachetime');
        if ($_cache > 0 && filemtime($cacheFile) + $_cache < time())
            return false;
        return true;
    }

    /**
     * 显示输出内容
     * @param string $template 模板
     * @param string $id 识别ID
     * @param string $template
     */
    public function display($template = '', $id = '') {
        $content = $this->fetch($template, $id);
        !APP_DEBUG && $this->obGzip($content);
        header('Content-type:text/html;charset=utf-8');
        header('X-Powered-By:SmartPHP.cn');
        header('Renderer:webkit');
        header('Content-Length:' . strlen($content));
        echo $content;
    }

    /**
     * Gzip数据压缩传输 如果客户端支持
     *
     * @param string $content
     * @return string
     */
    public function obGzip(&$content) {
        if (!headers_sent() && extension_loaded("zlib") && strstr($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip")) {
            $content = gzencode($content, 9);
            header('Content-Encoding:gzip');
            header('Vary:Accept-Encoding');
        }
        return $content;
    }

    /**
     * 清除全部赋值
     */
    public function clearAllAssign() {
        $this->_vars = [];
    }

    /**
     * 清除所有模板缓存
     *
     * @param string ...$template
     */
    public function clearAllCache() {
        File::emptyDir(rtrim(RUNTIME_PATH, '\\/') . DS . APP_NAME . DS . 'Cache');
    }

    /**
     * 清除所有模板编译
     *
     * @param string ...$template
     */
    public function clearAllCompile() {
        File::emptyDir(rtrim(RUNTIME_PATH, '\\/') . DS . APP_NAME . DS . 'Compile');
    }

    /**
     * 清除模板缓存
     *
     * @param string ...$template
     */
    public function clearCache(...$template) {
        array_map(function ($var) {
            File::remove($this->getCacheFile($this->getTemplateFile($var)));
        }, $template);
    }

    /**
     * 清除模板编译
     *
     * @param string ...$template
     */
    public function clearCompile(...$template) {
        array_map(function ($var) {
            File::remove($this->getCompileFile($this->getTemplateFile($var)));
        }, $template);
    }

    /**
     * 清除赋值
     *
     * @param string ...$vars
     */
    public function clearAssign(...$vars) {
        array_map(function ($var) {
            unset($this->_vars[$var]);
        }, $vars);
    }

    private $_compileTime;

    /**
     * 返回编译时间
     *
     * @return string
     */
    public function getCompileTime() {
        return $this->_compileTime;
    }

    /**
     * 获取语言
     */
    private function getLang($key) {
        return Lang::get($key);
    }

    /**
     * 获取URL
     */
    private function getUrl($url) {
        return Url::parse($url);
    }

    /**
     * 获取配制
     */
    private function getConf($conf) {
        return Config::get($conf);
    }

    private $_preg, $_replace, $left, $right, $_left, $_right;

    /**
     * 编译
     *
     * @param string $templateFile
     * @param string $compileFile
     */
    private function compile($templateFile, $compileFile) {
        $content = trim(File::read($templateFile));
        $this->left = Config::get('view.left');
        $this->right = Config::get('view.right');
        $this->_left = '(?<!!)' . $this->stripPreg($this->left);
        $this->_right = '((?<![!]))' . $this->stripPreg($this->right);
        if (Config::get('view.layout'))
            $content = trim($this->parseLayout($content));
        $content = $this->compileInclude($content);
        // if (!APP_DEBUG) {
        $md5 = md5($content);
        if (is_file($compileFile)) {
            $check = File::read($compileFile, 8, 32);
            if ($md5 === $check) {
                return;
            }
        }
        // }
        //if (Config::get('VIEW_SHOW_TRACE')&&!\Help\Client::isAjax()) $content.=File::read(rtrim(SMARTPHP_PATH,'\\/').DS.'Tpl'.DS.'trace.tpl');
        if (!empty($content))
            $this->compileCode($content);
        Config::get('view.compresshtml') && Compress::html($content);
        File::write($compileFile, "<?php\n//" . (!empty($md5) ? $md5 : '') . "\ndefined('SMARTPHP_PATH') or exit('Error');?>" . $content);
    }

    /**
     * 编译代码
     *
     * @param string $content
     */
    private function compileCode(&$content) {
        $this->compileVar($content);
        !Config::get('view.php') && $this->replacePHP();
        $this->replaceFun();
        $content = preg_replace_callback('/' . $this->_left . 'literal' . $this->_right . '(.*?)' . $this->_left . '\/literal' . $this->_right . '/is', [$this, 'parseLiteral'], $content);
        $this->replaceUse();
        $content = preg_replace($this->_preg, $this->_replace, $content);
        $content = str_replace(['!' . $this->_right, '!' . $this->_right], [$this->_right, $this->_right], $content);
        $content = preg_replace_callback('/<!--###literal(\d+)###-->/is', [$this, 'restoreLiteral'], $content);
        $content = preg_replace_callback("/##XML(.*?)XML##/s", [$this, 'xmlSubstitution'], $content);
        $content = $this->parseString($content);
        return $content;
    }

    /**
     * 处理XML
     *
     * @param string $capture
     */
    private function xmlSubstitution($capture) {
        return "<?php echo '<?xml " . stripslashes($capture[1]) . " ?>'; ?>";
    }

    /**
     * 编译变量
     *
     * @param string $content
     */
    private function compileVar(&$content) {
        $content = preg_replace_callback('/(' . $this->_left . ')([^\d\s].+?)(' . $this->_right . ')/is', [$this, 'parseTag'], $content);
        return $content;
    }

    /**
     * 编译导入文件
     *
     * @param unknown $content
     */
    private function compileInclude($content) {
        $content = preg_replace_callback('/' . $this->_left . 'include\sfile=[\'"](.+?)[\'"]\s*?' . $this->_right . '/is', [$this, 'parseInclude'], $content);
        return $content;
    }

    /**
     * 解析布局
     *
     * @param string $content
     */
    private function parseLayout($content) {
        $layout = File::read($this->getTemplateFile(Config::get('view.layoutname')));
        $layout = $this->compileInclude($layout);
        $pattern = '/' . $this->_left . 'block\sname=[\'"](.+?)[\'"]\s*?' . $this->_right . '(.*?)' . $this->_left . '\/block' . $this->_right . '/is';
        if (preg_match($pattern, $layout)) {
            preg_replace_callback($pattern, [$this, 'parseBlock'], $content);
            $layout = $this->replaceBlock($layout);
            return str_replace(Config::get('view.layoutitem'), preg_replace($pattern, '', $content), $layout);
        } else
            return str_replace(Config::get('view.layoutitem'), $content, $layout);
    }

    /**
     * 解析导入文件
     *
     * @param array $content
     * @return string
     */
    private function parseInclude($content) {
        return $this->compileInclude(File::read($this->getTemplateFile(stripslashes($content[1]))));
    }

    /**
     * 解析标签
     *
     * @param array $content
     * @return string
     */
    private function parseTag($content) {
        $content = preg_replace_callback('/\$\w+((\.\w+)*)?/', [$this, 'parseVar'], stripslashes($content[0]));
        return $content;
    }

    /**
     * 解析变量
     *
     * @param array $var
     * @return string
     */
    private function parseVar($var) {
        if (empty($var[0]))
            return;
        $vars = explode('.', $var[0]);
        $var = array_shift($vars);
        $name = $var;
        foreach ($vars as $val)
            $name .= '["' . trim($val) . '"]';
        return $name;
    }

    /**
     * 处理模板语法
     */
    private function replaceUse() {
        $this->_preg[] = '/' . $this->_left . '(else if|elseif) (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . 'for (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . 'while (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '(loop|foreach) (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . 'if (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . 'else' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . "(eval|_)( |[\r\n])(.*?)" . $this->_right . '/is';
        $this->_preg[] = '/' . $this->_left . ':(.*?)' . $this->_right . '/is';
        $this->_preg[] = '/' . $this->_left . '_e (.*?)' . $this->_right . '/is';
        $this->_preg[] = '/' . $this->_left . '_p (.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '\/(if|for|loop|foreach|eval|while)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '(([_a-zA-Z][\w]*\(.*?\))|\$((\w+)(\[(\'|")?\$*\w*(\'|")?\])*(->)?(\w*)(\((\'|")?(.*?)(\'|")?\)|)))' . $this->_right . '/i';
        $this->_preg[] = "/(	| ){0,}(\r\n){1,}\";/";
        $this->_preg[] = '/' . $this->_left . '(\#|\*)(.*?)(\#|\*)' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . '\@(.*?)' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . '\#(.*?)' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . '\%([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . 'ViewUsageTime' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . 'UsageTime' . $this->_right . '/';
        $this->_preg[] = '/' . $this->_left . 'UsageMemory' . $this->_right . '/';
        $this->_replace[] = '<?php }else if (\\2){ ?>';
        $this->_replace[] = '<?php for (\\1) { ?>';
        $this->_replace[] = '<?php while (\\1) { ?>';
        $this->_replace[] = '<?php foreach (\\2) { ?>';
        $this->_replace[] = '<?php if (\\1){ ?>';
        $this->_replace[] = '<?php }else{ ?>';
        $this->_replace[] = '<?php \\3; ?>';
        $this->_replace[] = '<?php echo \\1; ?>';
        $this->_replace[] = '<?php echo \\1; ?>';
        $this->_replace[] = '<?php print_r(\\1); ?>';
        $this->_replace[] = '<?php } ?>';
        $this->_replace[] = '<?php echo \\1;?>';
        $this->_replace[] = '';
        $this->_replace[] = '';
        $this->_replace[] = '<?php echo $this->getConf("\\1");?>';
        $this->_replace[] = '<?php echo $this->getUrl("\\1");?>';
        $this->_replace[] = '<?php echo $this->getLang(\'\\1\');?>';
        $this->_replace[] = '<?php echo $this->getCompileTime();?>';
        $this->_replace[] = '<?php echo \Base\App::getUsageTime();?>';
        $this->_replace[] = '<?php echo \Base\App::getUsageMemory();?>';
    }

    /**
     * 解析函数
     *
     * @param array $content
     * @return string
     */
    private function parseFun($content) {
        $content = preg_replace_callback('/ (model|db)\((.*?)\)/', [$this, 'replaceFun'], stripslashes($content[0]));
        return $content;
    }

    /**
     * 替换PHP标签
     */
    private function replaceFun() {
        $this->_preg[] = '/' . $this->_left . '(.*?)(model|db)\((.*?)\)(.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '(.*?)(lang)\((.*?)\)(.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '(.*?)(config)\((.*?)\)(.*?)' . $this->_right . '/i';
        $this->_preg[] = '/' . $this->_left . '(.*?)(url)\((.*?)\)(.*?)' . $this->_right . '/i';

        $this->_replace[] = $this->left . '\\1 \Base\Loader::\\2(\\3)\\4' . $this->right;
        $this->_replace[] = $this->left . '\\1 \Base\Lang::get(\\3)\\4' . $this->right;
        $this->_replace[] = $this->left . '\\1 \Base\Config::get(\\3)\\4' . $this->right;
        $this->_replace[] = $this->left . '\\1 \Base\Url::parse(\\3)\\4' . $this->right;
    }

    /**
     * 替换PHP标签
     */
    private function replacePHP() {
        $this->_preg[] = '/<\?(=|php|)(.+?)\?>/is';
        $this->_replace[] = '&lt;?\\1\\2?&gt;';
    }

    /**
     * 转换标示符
     *
     * @param string $Tag
     * @return string
     */
    private function stripPreg($tag) {
        return str_replace(['{', '}', '(', ')', '|', '[', ']', '-', '+', '*', '.', '^', '?'], ['\{', '\}', '\(', '\)', '\|', '\[', '\]', '\-', '\+', '\*', '\.', '\^', '\?'], $tag);
    }

    private $_literal = [];

    /**
     * 替换页面中的literal标签
     *
     * @access private
     * @param string $content 模板内容
     * @return string|false
     */
    private function parseLiteral($content) {
        if (is_array($content))
            $content = $content[2];
        if (trim($content) == '')
            return '';
        $i = count($this->_literal);
        $parseStr = "<!--###literal{$i}###-->";
        $this->_literal[$i] = $content;
        return $parseStr;
    }

    /**
     * 还原被替换的literal标签
     *
     * @access private
     * @param string $tag literal标签序号
     * @return string|false
     */
    private function restoreLiteral($tag) {
        if (is_array($tag))
            $tag = $tag[1];
        $parseStr = $this->_literal[$tag];
        unset($this->_literal[$tag]);
        return $parseStr;
    }

    private $_block;

    /**
     * 记录当前页面中的block标签
     *
     * @access private
     * @param string $name block名称
     * @return string
     */
    private function parseBlock($name) {
        $this->_block[$name[1]] = $name[3];
        return '';
    }

    /**
     * 替换继承模板中的block标签
     *
     * @access private
     * @param string $content 模板内容
     * @return string
     */
    private function replaceBlock($content) {
        static $parse = 0;
        $begin = $this->_left;
        $end = $this->_right;
        $reg = '/(' . $begin . 'block\sname=[\'"](.+?)[\'"]\s*?' . $end . ')(.*?)' . $begin . '\/block' . $end . '/is';
        if (is_string($content)) {
            do {
                $content = preg_replace_callback($reg, [$this, 'replaceBlock'], $content);
            } while ($parse && $parse--);
            return $content;
        } elseif (is_array($content)) {
            return $this->_block[$content[2]] ?? $content[4];
        }
    }

    /**
     * 替换标签
     *
     * @param string $content
     * @return string|mixed
     */
    private function parseString($content) {
        $replace = Config::get('view.parsestring');
        return !empty($replace) ? str_replace(array_keys($replace), array_values($replace), $content) : $content;
    }

    /**
     * 返回编辑文件
     *
     * @param string $template
     * @return string
     */
    private function getCompileFile($template, $id = '') {
        return rtrim(RUNTIME_PATH, '\\/') . DS . APP_NAME . DS . 'Compile' . DS . APP_CONTROLLER . DS . APP_ACTION . DS . md5($template . '|' . $id) . Config::get('view.compilesuffix');
    }

    /**
     * 返回缓存文件
     *
     * @param string $template
     * @return string
     */
    private function getCacheFile($template, $id = '') {
        return rtrim(RUNTIME_PATH, '\\/') . DS . APP_NAME . DS . 'Cache' . DS . APP_CONTROLLER . DS . APP_ACTION . DS . md5($template . '|' . $id) . Config::get('view.cachesuffix');
    }

    /**
     * 获取模板路径
     *
     * @param string $template
     * @return string
     */
    private function getTemplateFile($template) {
        if (empty($template))
            $template = APP_CONTROLLER . ':' . APP_ACTION;
        elseif (is_readable($template))
            return $template;
        elseif (stristr($template, ':') === false)
            $template = APP_CONTROLLER . ':' . $template;
        $view = str_ireplace(':', DS, trim($template, ':')) . Config::get('view.tplsuffix');
        $theme = Config::get('view.theme') ?: '';
        if (!empty($theme)) {
            foreach ([APP_DIRECTORY, COMMON_PATH] as $path) {
                if (is_readable(($path = rtrim($path, '\\/') . DS . 'View' . DS . $theme . DS . $view))) {
                    $tpl = $path;
                    break;
                }
            }
        }
        if (empty($tpl)) {
            foreach ([APP_DIRECTORY, COMMON_PATH] as $path) {
                if (is_readable(($path = rtrim($path, '\\/') . DS . 'View' . DS . $view))) {
                    $tpl = $path;
                    break;
                }
            }
        }
        empty($tpl) && trigger_error('模板不存在:' . $view);
        return $tpl;
    }

}
